import { nextTick, onScopeDispose, ref, type Ref, type UnwrapRef } from 'vue'

enum LoadStatus {
  PRE_LOAD,
  LOADING,
  POST_LOAD
}

type Listener<T> = (state: UnwrapRef<T>) => void

export interface UseStateOptions<T> {
  /**
   * 数据加载函数
   */
  fn: () => Promise<UnwrapRef<T>>
  /**
   * 初始值
   */
  initial: T
  /**
   * 自动执行 initialize 函数
   */
  immediate?: boolean
}

export function useState<T>(options: UseStateOptions<T>): {
  state: Ref<UnwrapRef<T>>
  initialize: (reload?: boolean) => void
  tryOnInitialized: (listener: Listener<T>, sync?: boolean) => void
} & [
  state: Ref<UnwrapRef<T>>,
  initialize: (reload?: boolean) => void,
  tryOnInitialized: (listener: Listener<T>, sync?: boolean) => void
] {
  const state = ref(options.initial)

  let _load = LoadStatus.PRE_LOAD
  const _listener: Listener<T>[] = []

  function removeListener(listener: Listener<T>) {
    for (let i = _listener.length; i--; ) {
      if (_listener[i] === listener) {
        _listener.splice(i, 1)
        break
      }
    }
  }

  function tryOnInitialized(listener: Listener<T>, sync?: boolean) {
    if (_load === LoadStatus.POST_LOAD) {
      // 异步的作用是不用考虑 tryOnInitialized 函数执行的位置，不会卡“暂时性死区”的 bug
      sync ? listener(state.value!) : nextTick(() => listener(state.value!))
    } else if (!_listener.includes(listener)) {
      _listener.push(listener)

      // 要考虑极限边界情况（组件已销毁，但数据仍未加载完毕）
      onScopeDispose(() => removeListener(listener))
    }
  }

  function initialize(reload = false) {
    // 需要重载且属于加载中 || 不需要重载且不属于未加载
    if ((reload && _load === LoadStatus.LOADING) || (!reload && _load !== LoadStatus.PRE_LOAD)) return

    _load = LoadStatus.LOADING

    options.fn().then(
      (data) => {
        state.value = data

        _load = LoadStatus.POST_LOAD

        _listener.forEach((fn) => fn(state.value))
        _listener.length = 0
      },
      () => {
        _load = LoadStatus.PRE_LOAD
      }
    )
  }

  options.immediate && nextTick(initialize)

  return {
    state,
    initialize,
    tryOnInitialized,
    *[Symbol.iterator]() {
      yield state
      yield initialize
      yield tryOnInitialized
    }
  } as any
}
